{# Base layout every page extends. Blocks: title, favicon, head_extra, navbar, page_body, content, footer, scripts. Override ``content`` for an ordinary page; reach for ``page_body`` only when a page needs to replace the whole main wrapper. ``#app-content`` is the element htmx swaps fragments into (see ``render()`` in rendering.py and the two layouts under layouts/). #} {% from "components/macros/feedback.html" import toast_region %} {% from "components/macros/layout.html" import theme_toggle, dialog %} {% block title %}{{ project_name }}{% endblock %} {# Favicon: SVG only. Modern browsers handle it natively (auto-scales, respects prefers-color-scheme inside the SVG, and the bytes are smaller than maintaining a multi-resolution ICO). Older browsers and bots that auto-request /favicon.ico will 404 — harmless. #} {% block favicon %}{% endblock %} {# Tailwind + DaisyUI, precompiled — no runtime JIT, no CDN. Built into static/dist by the asset pipeline; static() returns the fingerprinted URL once a build exists and the raw source path before then. #} {# Applies the stored theme to the document element before the stylesheet paints; a sync script on purpose (see static/js/theme.js). #} {# htmx. History snapshots are disabled deliberately: htmx's default back-button behavior saves the live DOM to localStorage and re-injects it on history navigation. This DOM is full of Alpine-expanded templates (x-if/x-for clones, x-teleport copies) and script tags — restoring a snapshot re-runs scripts ("identifier already declared") and makes Alpine re-expand already-expanded templates, duplicating the whole page once per back/forward press. historyCacheSize 0 makes every restore a cache miss; refreshOnHistoryMiss turns a miss into a clean full-page load instead of an AJAX restore. #} {# responseHandling (first matching rule wins, RegExp.test semantics): a 422 carries a re-rendered form and must swap; other 4xx/5xx never swap and raise htmx:responseError, which app.js turns into a toast. #} {# SSE extension (hx-ext="sse") for job progress streams; registers against the global htmx, so it must follow core. #} {# Alpine.js — the collapse plugin MUST load before core so its directive is registered when Alpine inits. Both deferred, plugin first. #} {# Custom styles #} {% block head_extra %}{% endblock %} {% block navbar %}
{{ project_name }}
{{ theme_toggle() }} Dashboard
{% endblock %} {% block page_body %}
{% block content %}{% endblock %}
{% endblock %} {% block footer %}{% endblock %} {{ toast_region() }} {{ dialog() }} {% if auth_enabled %}{% endif %} {% block scripts %}{% endblock %}